Telegram Group & Telegram Channel
🐍 Задача с подвохом: Декораторы и мутабельные ловушки

Условие:

Что выведет следующий код и почему?


def memoize(fn):
cache = {}
def wrapper(arg):
if arg in cache:
print("Из кэша")
return cache[arg]
else:
result = fn(arg)
cache[arg] = result
return result
return wrapper

@memoize
def add_to_list(val, lst=[]):
lst.append(val)
return lst

res1 = add_to_list(1)
res2 = add_to_list(2)
res3 = add_to_list(1)

print(res1)
print(res2)
print(res3)


Вопрос:
Что будет выведено? Где здесь двойной подвох?

🔍 Разбор:

На первый взгляд кажется, что:

1. add_to_list(1) вернёт [1]
2. add_to_list(2) вернёт [2]
3. add_to_list(1) снова вызовет функцию (или достанет из кэша)

Но тут два подвоха:

Подвох №1: изменяемый аргумент по умолчанию

Аргумент lst=[] создаётся один раз при определении функции. Все вызовы без передачи списка будут использовать один и тот же список.

Подвох №2: кэширование по ключу

Декоратор memoize кэширует результат по ключу arg. Но функция возвращает список, который изменяется при каждом вызове. Даже если кэш сработает, вы получите тот же объект списка, который менялся между вызовами!

🧮 Что реально произойдёт:

- `res1 = add_to_list(1)` → функция вызвана, список становится `[1]`
- `res2 = add_to_list(2)` → функция вызвана снова (новый аргумент), список становится `[1, 2]`
- `res3 = add_to_list(1)` → аргумент `1` есть в кэше, сработает ветка `print("Из кэша")` и вернётся **ссылку на тот же изменённый список**

🔢 **Вывод:**

```
[1, 2]
[1, 2]
Из кэша
[1, 2]
```

Все результаты указывают на один и тот же изменённый список.

💥 **Почему это важно:**

1️⃣ **Изменяемые аргументы по умолчанию** сохраняются между вызовами
2️⃣ **Кэширование мутабельных объектов** может привести к неожиданным результатам: при возврате списка вы возвращаете не "результат на момент вычисления", а ссылку на объект, который может измениться позже

🛡️ **Как исправить:**

1️⃣ Использовать `lst=None` и инициализировать внутри функции:
```python
def add_to_list(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
```

2️⃣ Если кэшировать мутабельные объекты, лучше возвращать **копию**:
```python
import copy
cache[arg] = copy.deepcopy(result)
```

**Вывод:**

Декораторы + мутабельные аргументы = ловушка даже для опытных разработчиков. Особенно, когда мутабельные объекты кэшируются и меняются за кулисами.


@pythonl



tg-me.com/pythonl/4798
Create:
Last Update:

🐍 Задача с подвохом: Декораторы и мутабельные ловушки

Условие:

Что выведет следующий код и почему?


def memoize(fn):
cache = {}
def wrapper(arg):
if arg in cache:
print("Из кэша")
return cache[arg]
else:
result = fn(arg)
cache[arg] = result
return result
return wrapper

@memoize
def add_to_list(val, lst=[]):
lst.append(val)
return lst

res1 = add_to_list(1)
res2 = add_to_list(2)
res3 = add_to_list(1)

print(res1)
print(res2)
print(res3)


Вопрос:
Что будет выведено? Где здесь двойной подвох?

🔍 Разбор:

На первый взгляд кажется, что:

1. add_to_list(1) вернёт [1]
2. add_to_list(2) вернёт [2]
3. add_to_list(1) снова вызовет функцию (или достанет из кэша)

Но тут два подвоха:

Подвох №1: изменяемый аргумент по умолчанию

Аргумент lst=[] создаётся один раз при определении функции. Все вызовы без передачи списка будут использовать один и тот же список.

Подвох №2: кэширование по ключу

Декоратор memoize кэширует результат по ключу arg. Но функция возвращает список, который изменяется при каждом вызове. Даже если кэш сработает, вы получите тот же объект списка, который менялся между вызовами!

🧮 Что реально произойдёт:

- `res1 = add_to_list(1)` → функция вызвана, список становится `[1]`
- `res2 = add_to_list(2)` → функция вызвана снова (новый аргумент), список становится `[1, 2]`
- `res3 = add_to_list(1)` → аргумент `1` есть в кэше, сработает ветка `print("Из кэша")` и вернётся **ссылку на тот же изменённый список**

🔢 **Вывод:**

```
[1, 2]
[1, 2]
Из кэша
[1, 2]
```

Все результаты указывают на один и тот же изменённый список.

💥 **Почему это важно:**

1️⃣ **Изменяемые аргументы по умолчанию** сохраняются между вызовами
2️⃣ **Кэширование мутабельных объектов** может привести к неожиданным результатам: при возврате списка вы возвращаете не "результат на момент вычисления", а ссылку на объект, который может измениться позже

🛡️ **Как исправить:**

1️⃣ Использовать `lst=None` и инициализировать внутри функции:
```python
def add_to_list(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
```

2️⃣ Если кэшировать мутабельные объекты, лучше возвращать **копию**:
```python
import copy
cache[arg] = copy.deepcopy(result)
```

**Вывод:**

Декораторы + мутабельные аргументы = ловушка даже для опытных разработчиков. Особенно, когда мутабельные объекты кэшируются и меняются за кулисами.


@pythonl

BY Python/ django


Warning: Undefined variable $i in /var/www/tg-me/post.php on line 283

Share with your friend now:
tg-me.com/pythonl/4798

View MORE
Open in Telegram


Python django Telegram | DID YOU KNOW?

Date: |

Dump Scam in Leaked Telegram Chat

A leaked Telegram discussion by 50 so-called crypto influencers has exposed the extraordinary steps they take in order to profit on the back off unsuspecting defi investors. According to a leaked screenshot of the chat, an elaborate plan to defraud defi investors using the worthless “$Few” tokens had been hatched. $Few tokens would be airdropped to some of the influencers who in turn promoted these to unsuspecting followers on Twitter.

Traders also expressed uncertainty about the situation with China Evergrande, as the indebted property company has not provided clarification about a key interest payment.In economic news, the Commerce Department reported an unexpected increase in U.S. new home sales in August.Crude oil prices climbed Friday and front-month WTI oil futures contracts saw gains for a fifth straight week amid tighter supplies. West Texas Intermediate Crude oil futures for November rose $0.68 or 0.9 percent at 73.98 a barrel. WTI Crude futures gained 2.8 percent for the week.

Python django from fr


Telegram Python/ django
FROM USA